package model.objects;

import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.List;
import physics.EuclideanVector;
import physics.collision.Collidable;
import physics.collision.CollisionEvent;
import physics.collision.CollisionEvent.Direction;
import physics.collision.CollisionStrategy;
import physics.movement.MovementStrategy;


/**
 * Class for physical objects that populate a game space.
 * Implements Collidable which gives logic for what to do when two objects
 * collide.
 * 
 * @author Sam Rang
 *         modified by geo4
 * 
 */
public class GameObject implements Collidable {
    private CollisionStrategy myCollisionStrategy;
    private MovementStrategy myMovementStrategy;
    private Point2D myVelocity;
    private Point2D myAcceleration;
    private List<Image> myImageList;
    private Rectangle2D myBounds;
    private Level myLevel;
    private boolean myIsOnGround = false;

    /**
     * Creates a new GameObject with a specified image, bounds, Collision
     * strategy and
     * Movement strategy.
     * 
     * @param level level in which the GameObject exists
     * @param bounds bounds of the object
     * @param image Image that represents the object on the Canvas
     * @param cs CollisionStrategy for handling the collision with another
     *        object
     * @param ms MovementStrategy for handling the movement of the object
     */
    public GameObject (Level level, Rectangle bounds, List<Image> image, CollisionStrategy cs,
                       MovementStrategy ms) {
        myImageList = image;
        myLevel = level;
        myBounds =
                new Rectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(),
                                       bounds.getHeight());
        myLevel = level;
        myCollisionStrategy = cs;
        myMovementStrategy = ms;
        myVelocity = new Point2D.Double(0, 0);
        myAcceleration = new Point2D.Double(0, 0);
    }

    /**
     * Returns the image representing the GameObject. This is determined by the
     * the object's velocity, whether it is on the ground, and what images it
     * was constructed with.
     * 
     * @return image image representing the object during its current movement
     */
    public Image getImage () {
        Image currentImage;
        if (myVelocity.getX() < 0 && myIsOnGround && myImageList.size() >= 2) {
            // Walk left
            currentImage = myImageList.get(1);
        }
        if (myVelocity.getX() > 0 && !myIsOnGround && myImageList.size() >= 3) {
            // Jump right
            currentImage = myImageList.get(2);
        }
        if (myVelocity.getX() < 0 && !myIsOnGround && myImageList.size() >= 4) {
            // Jump left
            currentImage = myImageList.get(3);
        }
        else {
            // Walk right (default)
            currentImage = myImageList.get(0);
        }
        return currentImage;
    }

    /**
     * Paints the image representing an object.
     * 
     * @param g graphics of JFrame
     */
    public void paint (Graphics g) {
        g.drawImage(getImage(), (int) myBounds.getCenterX(), (int) myBounds.getCenterY(), null);
    }

    /**
     * Returns the bounds of the object
     * 
     * @return bounds of the object
     */
    public Rectangle2D getBounds () {
        return myBounds;
    }

    /**
     * Returns a boolean of whether the object specified collied with this
     * object.
     * 
     * @param g object to check collision with
     * @return boolean that returns true if collision happened and false
     *         if there was no collision
     */
    public boolean collidedWith (GameObject g) {
        return myBounds.intersects(g.getBounds());
    }

    /**
     * Updates the position and state of the object.
     * 
     * @param elapsedTime time since the last update stored as a double
     * @param globalForces forces to be applied to the GameObject (like gravity)
     */
    public void update (double elapsedTime, EuclideanVector ... globalForces) {
        myMovementStrategy.updatePosition(myLevel, this, elapsedTime, globalForces);
    }

    @Override
    public CollisionEvent checkCollision (Collidable other) {
        Direction d = null;
        Rectangle2D otherBounds = other.getBounds();
        double xdiff = myBounds.getCenterX() - otherBounds.getCenterX();
        double ydiff = myBounds.getCenterY() - otherBounds.getCenterY();
        if (Math.abs(xdiff) < Math.abs(ydiff)) {
            if (ydiff < 0) {
                d = Direction.BOTTOM;
            }
            else {
                d = Direction.TOP;
            }
        }
        else {
            if (xdiff < 0) {
                d = Direction.LEFT;
            }
            else {
                d = Direction.RIGHT;
            }
        }
        return new CollisionEvent(this, other, d);

    }

    @Override
    public void handleCollision (CollisionEvent collisionEvent) {
        myCollisionStrategy.collisionAction(myLevel, collisionEvent);
    }

    /**
     * Sets whether the GameObject is currently on the ground and able to jump
     * off of it.
     * 
     * @param onGround true if the GameObject is resting on a solid object
     */
    public void setOnGround (boolean onGround) {
        myIsOnGround = onGround;
    }

    /**
     * State value representing if GameObject is on the ground and able to jump
     * off of it.
     * 
     * @return true if the GameObject is on a solid object
     */
    public boolean isOnGround () {
        return myIsOnGround;
    }

    /**
     * Gets a reference to the GameObject's movement strategy.
     * 
     * @return movement strategy of this instance of GameObject
     */
    public MovementStrategy getMovementStrategy () {
        return myMovementStrategy;
    }

    /**
     * Gets the x and y velocity of the GameObject.
     * 
     * @return the velocity of the GameObject represented by a Point.
     */
    public Point2D getVelocity () {
        return myVelocity;
    }

    /**
     * Sets the GameObject's velocity.
     * 
     * @param newVelocity velocity as a Point2D
     */
    public void setVelocity (Point2D newVelocity) {
        myVelocity.setLocation(newVelocity);
    }

    /**
     * Gets the x and y acceleration of the GameObject.
     * 
     * @return the acceleration of the GameObject represented by a Point.
     */
    public Point2D getAcceleration () {
        return myAcceleration;
    }

    /**
     * Sets the GameObject's acceleration.
     * 
     * @param newAcceleration velocity as a Point2D
     */
    public void setAcceleration (Point2D newAcceleration) {
        myAcceleration.setLocation(newAcceleration);
    }

    /**
     * Sets the top left corner of the GameObjects bounds to be the parameters
     * specified.
     * 
     * @param x x position of the left side of the GameObject's bounds
     * @param y y position of the top edge of the GameObject's bounds
     */
    public void setPosition (double x, double y) {
        myBounds.setFrame(x, y, myBounds.getWidth(), myBounds.getHeight());
    }

}
